/* $Id: AbstractDialog.java,v 1.13 2007/09/07 06:51:21 gigabass Exp $
 *
 * Copyright (c) Guido Ludwig. All rights reserved.
 * Use is subject to license terms.
 */

package de.guidoludwig.af;

/**
 * A Basic Dialog
 *
 * @author <a href="mailto:jtrade@gigabss.de">Guido Ludwig </a>
 * @version $Revision: 1.13 $
 */

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.factories.ButtonBarFactory;

public abstract class AbstractDialog extends JDialog {

	private static final AFProperties PROPERTIES = new AFProperties(AbstractDialog.class);

	private static final String CANCEL_ID = "dialog.cancel.name";
	private static final String OK_ID = "dialog.ok.name";


	// Instance Fields ********************************************************

	/**
	 * Indicates whether the dialog has been canceled or not. The value is reset
	 * to <code>false</code> in the #open method.
	 */
	private boolean canceled = false;

	// Fields for the lazily created frequently used dialog Actions ***********

	private Action cancelAction;

	private Action okAction;


	// Instance Creation ****************************************************

	/**
	 * Constructs an <code>AbstractDialog</code> with the given
	 * <code>Frame</code> as its owner using the given window title and the
	 * specified modal state.
	 * @param title resource string for the title

	 */
	public AbstractDialog(String title) {
		super(ApplicationFrame.INSTANCE.mainFrame(), title, true);
		configureWindowClosing();
	}

	// Building ***************************************************************

	/**
	 * Builds the dialog's content pane, packs it, sets the resizable property,
	 * and locates it on the screen. The dialog is then ready to be opened.
	 * <p>
	 *
	 * Subclasses should rarely override this method.
	 */
	protected void build() {
		JComponent content = buildContentPane();
		setContentPane(content);
		pack();
		setResizable();
		locateOnScreen();
	}

	/**
	 * Builds and returns the content pane, sets the border and puts an optional
	 * header component in the dialog's north.
	 * <p>
	 *
	 * Subclasses will rarely override this method.
	 *
	 * @return the dialog's content pane with a border set and optional header
	 *         in the north
	 */
	protected JComponent buildContentPane() {
		JComponent center = buildContent();
		center.setBorder(getDialogBorder());

		JComponent header = buildHeader();
		if (header == null)
			return center;

		JPanel panel = new JPanel(new BorderLayout());
		panel.add(header, BorderLayout.NORTH);
		panel.add(center, BorderLayout.CENTER);
		return panel;
	}

	/**
	 * Builds and returns the optional header component, which will be put in
	 * the dialog's north by the default #buildContentPane implementation. By
	 * default this method answers <code>null</code> and so, no header will be
	 * used.
	 * <p>
	 *
	 * A typical implementation answers an instance of <code>HeaderPane</code>
	 * or a similar component, see the following code example:
	 *
	 * <pre>
	 * protected JComponent buildHeader() {
	 * 	return new HeaderPanel(&quot;My Dialog&quot;,
	 * 			&quot;You can do click on the tabs below to ...&quot;, ResourceUtils
	 * 					.getIcon(MyResourceIDs.MY_DIALOG_ICON));
	 * }
	 * </pre>
	 *
	 * @return the built header component
	 */
	protected JComponent buildHeader() {
		return null;
	}

	// Abstract Building Behavior *********************************************

	/**
	 * Builds and answers the dialog's main content without header and border.
	 * Subclass must override this method. See the class comment for a detailed
	 * description how this method is part of the build process.
	 *
	 * @return the dialog's main content without header and border
	 */
	abstract protected JComponent buildContent();

	// Dialog Life Cycle ******************************************************

	/**
	 * Builds the dialog content, marks it as not canceled and makes it visible.
	 */
	public void open() {
		build();
		canceled = false;
		setVisible(true);
	}

	/**
	 * Closes the dialog: makes it invisible and disposes it, which in turn
	 * releases all required OS resources.
	 */
	public void close() {
		dispose();
	}

	/**
	 * Checks and answers whether the dialog has been canceled. This indicator
	 * is set in #doAccept and #doCancel.
	 *
	 * @return true indicates that the dialog has been canceled
	 */
	public boolean hasBeenCanceled() {
		return canceled;
	}

	// Configuration **********************************************************

	/**
	 * Configures the closing behavior: invokes #doCloseWindow instead of just
	 * closing the dialog. This allows subclasses to hook into the close process
	 * and perform a custom code sequence.
	 */
	protected void configureWindowClosing() {
		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowClosingHandler());
	}

	protected static final Border DIALOG_BORDER = new EmptyBorder(12, 10, 10,
			10);

	protected static final Border CARD_DIALOG_BORDER = new EmptyBorder(10, 6,
			7, 6);

	/**
	 * Returns the border that will be put around the content, which has been
	 * created by #buildContent.
	 * <p>
	 *
	 * Subclasses will typically use <code>DIALOG_BORDER</code> or
	 * <code>CARD_DIALOG_BORDER</code>, which is more narrow than the
	 * default.
	 *
	 * @return the dialog's border
	 */
	protected Border getDialogBorder() {
		return DIALOG_BORDER;
	}

	/**
	 * Sets the dialog's resizable state. By default dialogs are non-resizable;
	 * subclasses may override.
	 */
	protected void setResizable() {
		setResizable(false);
	}

	/**
	 * Locates the dialog on the screen. The default implementation sets the
	 * location relative to the parent.
	 * <p>
	 *
	 * Subclasses may choose to center the dialog or put it in a screen corner.
	 */
	protected void locateOnScreen() {
		setLocationRelativeTo(getParent());
	}

	/**
	 * Sets a button as default button in the dialog's root pane.
	 *
	 * @param button
	 *            the button to be set as default
	 */
	protected void setDefaultButton(JButton button) {
		getRootPane().setDefaultButton(button);
	}

	// Common Button Bars ***************************************************

	/**
	 * Builds and returns a button bar with an OK and a Cancel button.
	 */
	protected JComponent buildButtonBarWithOKCancel() {
		return buildButtonBar(getOKAction(), getCancelAction());
	}

	/**
	 * Builds a right aligned button bar with the 2 given actions. T The 1st
	 * action will be the default action for the dialog
	 */
	protected JComponent buildButtonBar(Action action1, Action action2) {
		JButton b1 = new JButton(action1);
		JPanel bar = ButtonBarFactory
				.buildOKCancelBar(b1, new JButton(action2));
		setDefaultButton(b1);
		bar.setBorder(Borders.BUTTON_BAR_GAP_BORDER);
		return bar;
	}

	/**
	 * Builds a right aligned button bar with the 2 given actions. T The 1st
	 * action will be the default action for the dialog
	 */
	protected JComponent buildButtonBar(Action action1, Action action2, Action action3) {
		JButton b1 = new JButton(action1);
		JPanel bar = ButtonBarFactory
				.buildOKCancelApplyBar(b1, new JButton(action2), new JButton(action3));
		setDefaultButton(b1);
		bar.setBorder(Borders.BUTTON_BAR_GAP_BORDER);
		return bar;
	}
	/**
	 * Builds and returns a button bar with an OK button.
	 */
	protected JComponent buildButtonBarWithOK() {
		JButton ok = new JButton(getOKAction());
		JPanel bar = ButtonBarFactory.buildOKBar(ok);
		setDefaultButton(ok);
		bar.setBorder(Borders.BUTTON_BAR_GAP_BORDER);
		return bar;
	}

	// Common Behavior ******************************************************

	/**
	 * Invokes <code>#doApply</code>, marks the dialog as uncanceled and
	 * finally closes it. This method is intended to apply value changes and
	 * then quit the dialog. Typically the core of this action is performed in
	 * <code>#doApply</code>.
	 *
	 * This method is invoked if an <i>Accept </i> or <i>OK </i> button created
	 * by <code>#createAcceptButton</code> or <code>createOKButton</code>
	 * has been clicked. Such buttons use a DispatchingActionListener that in
	 * turn delegates the action perform to this method.
	 */
	public void doAccept() {
		canceled = false;
		close();
	}

	/**
	 * Resets changed dialog values, marks the dialog as canceled and closes it.
	 *
	 * This method is invoked if a <i>Cancel </i> button created by
	 * <code>#createCancelButton</code> has been clicked. Such buttons use a
	 * DispatchingActionListener that in turn delegates the action perform to
	 * this method.
	 */
	public void doCancel() {
		canceled = true;
		close();
	}



	/**
	 * Performs the close window action that is invoked if the dialog is
	 * closing, for example by clicking the dialog's close button (often in the
	 * upper right corner). By default this methods performs a cancel via
	 * <code>#doCancel</code>.
	 */
	protected void doCloseWindow() {
		doCancel();
	}

	// Default Button Actions ***********************************************

	/**
	 * Lazily creates and returns the Cancel Action that invokes #doCancel.
	 *
	 * @return the lazily created Cancel Action
	 */
	public Action getCancelAction() {
		if (cancelAction == null) {
			cancelAction = new DispatchingAction(CANCEL_ID);
			cancelAction.setEnabled(true);
		}
		return cancelAction;
	}

	/**
	 * Lazily creates and returns the OK Action that invokes #doAccept.
	 *
	 * @return the lazily created OK Action
	 */
	public Action getOKAction() {
		if (okAction == null) {
			okAction = new DispatchingAction(OK_ID);
			okAction.setEnabled(true);
		}
		return okAction;
	}

	// Private Help Class *****************************************************

	// Invokes #doCloseWindow when the window is about to be closed.
	private class WindowClosingHandler extends WindowAdapter {
		@Override
		public void windowClosing(WindowEvent e) {
			doCloseWindow();
		}
	}

	/*
	 * An implementation of the Action interface that dispatches using an ID.
	 * Provides the behavior required for the default buttons created above.
	 */
	protected class DispatchingAction extends AbstractAction {

		/**
		 *
		 */
		private static final long serialVersionUID = 1L;
		protected final String id;

		protected DispatchingAction(String id) {
			super(PROPERTIES.getString(id));
			this.id = id;
		}

		/**
		 * Dispatches using this Action's stored id, then delegates to the
		 * appropriate dialog method that does the real work.
		 * @param evt
		 */
		@Override
		public void actionPerformed(ActionEvent evt) {
			if (id.equals(OK_ID)) {
				doAccept();
			} else if (id.equals(CANCEL_ID)) {
				doCancel();
			} else
				throw new IllegalStateException("Unknown action name" + id);
		}

		@Override
		// FIXME : why override ?
		public String toString() {
			return super.toString() + ": " + id;
		}
	}

}